vue中slot插槽分发

vue中slot插槽分发

将父组件的内容放到子组件指定的位置叫做内容分发

vue官网插槽
插槽,也就是slot,是组件的一块HTML模板,这块模板显示不显示、以及怎样显示由父组件来决定。 实际上,一个slot最核心的两个问题这里就点出来了,是显示不显示和怎样显示。
牢记一条准则:

父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。

单个插槽| 默认插槽 | 匿名插槽

父组件app.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div id="app">
<test-slot>
<span>我是父组件里的文字,但是我要被放到子组件里</span>
</test-slot>
</div>
</template>

<script>
import testSlot from './components/testSlot'
export default {
data(){
return {

}
},
components:{
testSlot
}
}
</script>

子组件testSlot.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
<h3>test-slot</h3>
//父组件里的span会替换掉slot所以这里的123是看不见的
//如果父组件在使用子组件testSlot的时候不在里面加内容则这里的slot会显示出来
<slot>123</slot>
</div>
</template>

<script>
export default {
data(){
return {

}
}
}
</script>

效果如图:
图1

具名插槽(多个插槽)

父组件

1
2
3
4
5
6
7
8
9
10
11
12
<base-layout>
<template slot="header">
<h1>Here might be a page title</h1>
</template>

<p>A paragraph for the main content.</p>
<p>And another one.</p>

<template slot="footer">
<p>Here's some contact info</p>
</template>
</base-layout>

子组件baseLayout.vue

1
2
3
4
5
6
7
8
9
10
11
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>

slot除了作用在模板上,还可直接用在一个普通的元素上:

1
2
3
4
5
6
7
8
<base-layout>
<h1 slot="header">Here might be a page title</h1>

<p>A paragraph for the main content.</p>
<p>And another one.</p>

<p slot="footer">Here's some contact info</p>
</base-layout>

我们还是可以保留一个未命名插槽,这个插槽是默认插槽,也就是说它会作为所有未匹配到插槽的内容的统一出口。上述两个示例渲染出来的 HTML 都将会是:

1
2
3
4
5
6
7
8
9
10
11
12
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>

作用域插槽| 带数据的插槽(将子组件的值传到父组件供使用)

作用域插槽简单来说就是父组件只管显示样式,数据由子组件来提供。
作用域插槽和单个插槽和具名插槽的区别,因为单个插槽和具名插槽不绑定数据,所以父组件中提供的模板既要包括样式又包括内容。

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
<div id="app">
<h2>app</h2>
<test-slot :items="items">
<template slot-scope="scope">
<span>{{ scope.addr }}</span>
<span>{{ scope.cname }}</span>
<span>{{ scope.age }}</span>
</template>
</test-slot>
</div>
</template>

<script>
import testSlot from './components/testSlot.vue'
export default {
data (){
return {
items:[
{ text:'文字1' , cname:'tom' , addr:'usa' },
{ text:'文字2' , cname:'wangwu' , addr:'uk' },
{ text:'文字3' , cname:'zhangsan' , addr:'un' }
]
}
},
methods:{

},
components:{
testSlot
}
}
</script>

子组件testSlot.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div class="hello">
<slot :cname="items[2].cname"></slot>
<slot :addr="items[2].addr"></slot>
<slot age="18"></slot>
</div>
</template>

<script>
export default {

data () {
return {
num:100
}
},
props:['items'],
methods:{

},
created(){
console.log('items',this.$props.items);
}
}
</script>

结果图:
图2
再举一个明显的例子:
父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
<div class="father">
<h3>这里是父组件</h3>
<!--第一次使用:用flex展示数据-->
<child>
<template slot-scope="user">
<div class="tmpl">
<span v-for="item in user.data">{{item}}</span>
</div>
</template>

</child>

<!--第二次使用:用列表展示数据-->
<child>
<template slot-scope="user">
<ul>
<li v-for="item in user.data">{{item}}</li>
</ul>
</template>

</child>

<!--第三次使用:直接显示数据-->
<child>
<template slot-scope="user">
{{user.data}}
</template>

</child>

<!--第四次使用:不使用其提供的数据, 作用域插槽退变成匿名插槽-->
<child>
我就是模板
</child>
</div>
</template>

子组件child.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="child">

<h3>这里是子组件</h3>
// 作用域插槽
<slot :data="data"></slot>
</div>
</template>

export default {
data: function(){
return {
data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
}
}
}

结果如图所示:
图3

作用域插槽深入理解

首先父组件中slot-scope其实就是标签的属性集合,也就是一个对象。这里,如第一个例子中属性scope的值是随便起的变量名“scope”,这个对象正是子组件传递过来的标签属性集合。因此我们可以通过标签属性绑定数据,父组件模板通过scope属性指定一个变量来接收。
由于scope是scope对象,所以获取子组件数据需要用scope.addr的方式。
为什么子组件需要参数props:['items']?因为父组件给子组件传递了参数<test-slot :items="items">

作用域插槽模板“渲染”过程

1. 常规组件的情况
父组件模版

1
2
3
4
5
6
7
8
<div class="parent">
<child>
<template scope="aaa">
<span>hello from parent</span>
<span>{{ aaa.text }}</span>
</template>
</child>
</div>

子组件

1
2
3
<div class="child">
<slot text="hello from child"></slot>
</div>

渲染过程:

  1. 丢弃父组件的内容,用子组件内容替换,变成:

    1
    2
    3
    4
    5
    <div class="parent">
    <div class="child">
    <slot text="hello from child"></slot>
    </div>
    </div>
  2. 此时丢弃的内容为:

    1
    2
    3
    4
    <template scope="aaa">
    <span>hello from parent</span>
    <span>{{ aaa.text }}</span>
    </template>
  3. 由于子组件具备插口,把原本丢弃的内容插到插口上:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <div class="parent">
    <div class="child">
    <slot text="hello from child">
    <template scope="aaa">
    <span>hello from parent</span>
    <span>{{ aaa.text }}</span>
    </template>
    </slot>
    </div>
    </div>
  4. 去掉自定义HTML标签

    1
    2
    3
    4
    5
    6
    <div class="parent">
    <div class="child">
    <span>hello from parent</span>
    <span>hello from child</span>
    </div>
    </div>

2. 列表组件的情况
父组件模版(列表)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div class="parent">
<my-awesome-list :items="items">
<!--之所以使用绑定是因为属性items的值是一个引用了Vue实例data里面的数组,而不是一个普通的字符串-->
<template scope="bbb">
<li class="my-fancy-item">{{ bbb.text }}</li>
</template>
</my-awesome-list>
</div>
<<script>
//注册组件,必须声明 items 变量,因为父组件里面带有“:items="items"”
Vue.component('my-awesome-list', {
props: ['items'],
//这里的模板中的 items 就是接收了父组件data里面的 items 数组。
template: "<ul><slot v-for='item in items' :text='item.text'></slot></ul>"
});
//实例化Vue,必须具备 items 数组,供子组件v-for指令循环使用。
var vtest = new Vue({
el: '#select',
data: { items: [{test:1},{test:2}] }
});
</script>

子组件(列表)

1
2
3
<ul>
<slot v-for="item in items" :text="item.text"></slot>
</ul>

渲染过程:

  1. 丢弃父组件的内容,用子组件内容替换,变成:

    1
    2
    3
    4
    5
    6
    7
    <div class="parent">
    <ul>
    <slot name="item" v-for="item in items" :text="item.text">
    <!-- 这里写入备用内容 -->
    </slot>
    </ul>
    </div>
  2. 此时丢弃的内容为:

    1
    2
    3
    <template slot="item" scope="bbb">
    <li class="my-fancy-item">{{ bbb.text }}</li>
    </template>
  3. 由于子组件具备插口,把原本丢弃的内容插到插口上:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div class="parent">
    <ul>
    <slot v-for="item in items" :text="item.text">
    <template scope="bbb">
    <li class="my-fancy-item">{{ bbb.text }}</li>
    </template>
    </slot>
    </ul>
    </div>
  4. 标签具备v-for指令,它会进行循环,标签的text属性动态绑定,得到下面状态:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <div class="parent">
    <ul>
    <slot text="1">
    <template scope="bbb">
    <li class="my-fancy-item">{{ bbb.text }}</li>
    </template>
    </slot>
    <slot text="2">
    <template scope="bbb">
    <li class="my-fancy-item">{{ bbb.text }}</li>
    </template>
    </slot>
    ……
    </ul>
    </div>

可见 Vue 实例参数data里面必须有items属性,结构为[{text:value}]

  1. 去掉自定义HTML标签